﻿/*
 * PegDB Class
 * Karim Sultan (karimsultan@hotmail.com)
 * October 2009
 * 
 * This class represents the database for PegEdit which draws images
 * compatible with Windell Oskay's addictive Peggy2.
 * 
 * Free software under LGPL 2.1.
 */

/*
Example XML format:
<PegDB>
<info>
 <title>PegEdit DB</title>
 <version>1.0<version>
 <created>[date]</created>
</info>

<preferences>
 <property name="name" value="value"/>
 <property name="palette" value="Blue LEDs">
</preferences>

<images>
 <image Name="Peg Edit" Encoding="Base64" Width="25" Height="25" Created="Oct 31 2009">
  ASASASDASDDAF==
 </image>
</images>
</PegDB>
*/


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;


namespace PegEdit
{
   class PegDB
   {
      /*
       * Members
       */

      private const string DBTITLE = "PegEdit DB";
      private const string DBVERSION = "1.0";
      private const string DBNAME = "pegeditdb.xml";

      private const string IMAGE_ENCODING = "Base64";

      private string MARQUIS =
         "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw8AAAAAAAAAAAAAAAAAAAAAAA" +
         "AAAA8FBQUFDwEADw8PDwAAAA8PDw8AAAAAAAAPBQ8PBQ8BDwUFBQUPAQ8FBQUFDwEA" +
         "AAAADwUPDwUPAQ8FDw8FDwEPBQ8PBQ8BAAAAAA8FBQUFDwEPBQUFBQ8BDwUFBQUPAQ" +
         "AAAAAPBQUPDwEADwUFDw8BAAAPDwUFDwEAAAAADwUPAQEAAA8FDwEAAAAAAAAPBQ8B" +
         "AAAAAA8FDwEAAAAPBQ8BAAAAAAAADwUPAQAAAAAPBQ8BAAAADwUFDw8BAAAPDwUFDw" +
         "EAAAAADwUPAQAAAAEPBQUPAQAADwUFBQ8BAAAAAAEPAQAAAAAAAQ8PDwEAAAEPDw8B" +
         "AAAAAAAAAQAAAAAAAAABAQEAAAAAAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
         "AAAAAAAA8PDw8AAAAAAAAPAAAADwAAAAAPAAAAAA8FBQUFDwEAAAAPBQ8BDwUPAQAP" +
         "BQ8BAAAPBQUPDwEAAAAADwUPAQAPAQAPDwUPDwEADwUPAQAAAAAAAA8FDwEADwEPBQ" +
         "UFBQUPAA8FBQ8PAQAAAAAPBQ8BDwUPAQ8PBQ8PAQAPBQUPDwEAAA8PBQUPAQ8FDwEA" +
         "DwUPAQAADwUPAQAAAA8FBQUFDwEPBQ8BAA8FDwEAAA8FBQ8PAQAPBQ8PBQ8BDwUPAQ" +
         "APBQ8BAAAPBQUFBQ8ADwUFBQUPAQ8FDwEADwUPAQAAAQ8PDw8BAAEPDw8PAQABDwEA" +
         "AAEPAQAAAAABAQEBAAAAAQEBAQAAAAEAAAAAAQAAAA==";

      /*
       * 
       * PUBLIC METHODS
       * 
       */

      /// <summary>
      /// Constructor.  If no DB exists, it will create one.
      /// </summary>
      public PegDB()
      {
         // Create DB if none
         if (!this.Exists())
            this.Create();
      }


      /// <summary>
      /// Loads the value of a specified property
      /// </summary>
      /// <param name="prop">The name of the property to load.</param>
      /// <returns>Property value, or null on failure.</returns>
      public string LoadProperty(string prop)
      {
         // Validate
         if (prop == null) return (null);
         if (prop == "") return (null);

         XmlNode node;

         try
         {
            // Create a doc and load Database into it
            XmlDocument doc = new XmlDocument();
            doc.Load(DBNAME);

            // Locate specific node using xpath:
            // PegDB -> Properties -> Property Name='prop'
            XmlElement root = doc.DocumentElement;
            node = root.SelectSingleNode("/PegDB/Properties/Property[@Name='" + prop + "']");
         }
         catch (Exception ex)
         {
            // Pass exception up the chain
            throw (ex);
         }

         // No property?
         if (node == null) return (null);

         // Retrieve the "Name" attribute
         return (node.Attributes["Value"].InnerText);
      }


      /// <summary>
      /// Determines if a specified image exists in the database.
      /// </summary>
      /// <param name="name">The name of the image to check.</param>
      /// <returns>True if the image exists.</returns>
      public bool ImageExists(string name)
      {
         // Validate
         if (name == null) return (false);
         if (name == "") return (false);

         XmlNode node;

         try
         {
            // Create a doc and load Database into it
            XmlDocument doc = new XmlDocument();
            doc.Load(DBNAME);

            // Locate specific node using xpath:
            // PegDB -> Images -> (n) Image -> Name='name'
            XmlElement root = doc.DocumentElement;
            node = root.SelectSingleNode("/PegDB/Images/Image[@Name='" + name + "']");

            if (node == null)
               return (false);
            else
               return (true);
         }
         catch (Exception ex)
         {
            // Pass exception up the chain
            throw (ex);
         }

      }


      /// <summary>
      /// Creates a list of names of all images in the database.
      /// </summary>
      /// <returns>A string[] of all image names, or null if none.</returns>
      public string[] ListImages()
      {
         XmlNodeList nodelist;

         try
         {
            // Create a doc and load Database into it
            XmlDocument doc = new XmlDocument();
            doc.Load(DBNAME);

            // Generate a nodelist using xpath:
            // PegDB -> Images -> (n) Image -> (n) Name
            XmlElement root = doc.DocumentElement;
            nodelist = root.SelectNodes("/PegDB/Images/Image");
         }
         catch (Exception ex)
         {
            // Pass exception up the chain
            throw (ex);
         }

         if (nodelist.Count == 0)
            return (null);

         // Size names array accordingly
         string[] names = new string[nodelist.Count];

         int index = 0;
         foreach (XmlNode node in nodelist)
         {
            // Retrieve the "Name" attribute
            names[index] = node.Attributes["Name"].InnerText;
            index++;
         }

         // Send back the list.  Use names.Length to determine count.
         return (names);
      }


      /// <summary>
      /// Loads and decodes a specified image from the database directly into a PegImage object.
      /// </summary>
      /// <param name="image">A PegImage object.</param>
      /// <param name="name">The name of the image to load from the DB.</param>
      public void LoadImage(PegImage image, string name)
      {
         // Validate
         if ((image == null) || (name == null))
            return;
         if (name == "") return;

         XmlNode node;

         try
         {
            // Create a doc and load Database into it
            XmlDocument doc = new XmlDocument();
            doc.Load(DBNAME);

            // Locate specific node using xpath:
            // PegDB -> Images -> Image -> Name = 'name'
            XmlElement root = doc.DocumentElement;
            node = root.SelectSingleNode("/PegDB/Images/Image[@Name='" + name + "']");
         }
         catch (Exception ex)
         {
            // Pass it up the chain
            throw (ex);
         }

         // Check for failure
         if (node == null) return;

         // Decode Base64 image data and populate the image object with it
         image.FromBase64(node.InnerText.Trim());
      }


      /// <summary>
      /// Saves an image to the database.
      /// </summary>
      /// <param name="image">The PegImage object representing the image.</param>
      /// <param name="name">The name of the image to be saved.</param>
      public void SaveImage(PegImage image, string name)
      {
         // Validate
         if (name == null) return;
         if (name == "") return;

         try
         {
            // Create a document and load database xml
            XmlDocument doc = new XmlDocument();
            doc.Load(DBNAME);

            // Search for existing image node first
            XmlElement root = doc.DocumentElement;
            XmlNode node = root.SelectSingleNode("/PegDB/Images/Image[@Name='" + name + "']");

            if (node == null)
            {
               // Get parent root for all properties using xpath:
               // PegDB -> Properties (root) -> (n) child:Property
               node = root.SelectSingleNode("/PegDB/Images");

               // Create the new Image element and add the image data
               XmlNode imagenode = doc.CreateElement("Image");
               imagenode.InnerText = image.ToString(PegImage.FORMAT_TYPE.IMAGE_BASE64);

               XmlAttribute attrnode;

               // Create the Name attribute
               attrnode = doc.CreateAttribute("Name");
               attrnode.Value = name;
               imagenode.Attributes.Append(attrnode);

               // Create the Width attribute
               attrnode = doc.CreateAttribute("Width");
               attrnode.Value = image.Width.ToString();
               imagenode.Attributes.Append(attrnode);

               // Create the Height attribute
               attrnode = doc.CreateAttribute("Height");
               attrnode.Value = image.Height.ToString();
               imagenode.Attributes.Append(attrnode);

               // Create the date Cretted attribute
               attrnode = doc.CreateAttribute("Created");
               attrnode.Value = DateTime.Now.ToString();
               imagenode.Attributes.Append(attrnode);

               // Create the Encoding attribute
               attrnode = doc.CreateAttribute("Encoding");
               attrnode.Value = IMAGE_ENCODING;
               imagenode.Attributes.Append(attrnode);

               // Put the node in the parent node
               node.AppendChild(imagenode);
            }
            else
            {
               // Node exits; so just overwrite value
               node.InnerText = image.ToString(PegImage.FORMAT_TYPE.IMAGE_BASE64);

               node.Attributes["Width"].Value = image.Width.ToString();
               node.Attributes["Height"].Value = image.Height.ToString();
               node.Attributes["Created"].Value = DateTime.Now.ToString();
               node.Attributes["Encoding"].Value = IMAGE_ENCODING;
               node.ParentNode.ReplaceChild(node, node);
            }

            // Update the database
            doc.Save(DBNAME);

         }
         catch (Exception ex)
         {
            // Pass it up the chain
            throw (ex);
         }
      }


      /// <summary>
      /// Deletes an image from the database.
      /// </summary>
      /// <param name="name">The name of the image to delete.</param>
      public void DeleteImage(string name)
      {
         // Validate
         if (name == null) return;
         if (name == "") return;

         try
         {
            // Create a doc and load database into it
            XmlDocument doc = new XmlDocument();
            doc.Load(DBNAME);

            // Locate specific node using xpath:
            // PegDB -> Images -> Image -> Name = 'name'
            XmlElement root = doc.DocumentElement;
            XmlNode node = root.SelectSingleNode("/PegDB/Images/Image[@Name='" + name + "']");

            // No node is as good as success
            if (node == null) return;

            // Delete it
            node.ParentNode.RemoveChild(node);

            // Save
            doc.Save(DBNAME);
         }
         catch (Exception ex)
         {
            // Pass it up the chain
            throw (ex);
         }
      }


      /// <summary>
      /// Saves a property to the DB.  Properrties are name, value pairs
      /// Overwrites entry if it already exists.
      /// </summary>
      /// <param name="prop">The string name of the property.</param>
      /// <param name="value">The string value of the property.</param>
      public void SaveProperty(string prop, string value)
      {
         // Validate
         if ((prop == null) || (value == null))
            return;

         // Ok for value to be "", but not the prop name
         if (prop == "") return;

         try
         {
            // Create a document and load database xml
            XmlDocument doc = new XmlDocument();
            doc.Load(DBNAME);

            // Search for existing node first
            XmlElement root = doc.DocumentElement;
            XmlNode node = root.SelectSingleNode("/PegDB/Properties/Property[@Name='" + prop + "']");

            if (node == null)
            {
               // Get parent root for all properties using xpath:
               // PegDB -> Properties (root) -> (n) child:Property
               node = root.SelectSingleNode("/PegDB/Properties");

               // Create the new Property node
               XmlNode propnode = doc.CreateElement("Property");

               // Create the Name attribute
               XmlAttribute name_attrnode = doc.CreateAttribute("Name");
               name_attrnode.Value = prop;

               // Create the Value attribute
               XmlAttribute value_attrnode = doc.CreateAttribute("Value");
               value_attrnode.Value = value;

               // Attach attributes to element
               propnode.Attributes.Append(name_attrnode);
               propnode.Attributes.Append(value_attrnode);


               // Put the node in the parent node
               node.AppendChild(propnode);
            }
            else
            {
               // Node exits; so just overwrite value
               node.Attributes["Value"].Value = value;
               node.ParentNode.ReplaceChild(node, node);
            }

            // Update the database
            doc.Save(DBNAME);

         }
         catch (Exception ex)
         {
            // Pass it up the chain
            throw (ex);
         }
      }

      /// <summary>
      /// Determines if the DB is present.
      /// </summary>
      /// <returns>True if file exists.</returns>
      public bool Exists()
      {
         return (File.Exists(DBNAME));
      }



      /*
       * 
       * PRIVATE METHODS
       * 
       */

      /// <summary>
      /// Creates a default XML DB.
      /// </summary>
      private void Create()
      {
         try
         {
            // Well use an XML Text Writer which streams direct to file
            XmlTextWriter writer = new XmlTextWriter(DBNAME, null);

            // Spruce it up
            writer.Formatting = Formatting.Indented;

            // Create the document
            writer.WriteStartDocument();
            writer.WriteComment("Generated and managed by Peg Edit via the PegDB class.");

            // Root node
            writer.WriteStartElement("PegDB");

            // Write out the info node
            writer.WriteStartElement("Info");
            writer.WriteElementString("Title", DBTITLE);
            writer.WriteElementString("Version", DBVERSION);
            writer.WriteElementString("Created", DateTime.Now.ToString());
            writer.WriteEndElement();  // Info

            // Write out the property node
            writer.WriteStartElement("Properties");
            writer.WriteStartElement("Property");
            writer.WriteAttributeString("Name", "Palette");
            writer.WriteAttributeString("Value", "White LEDs");
            writer.WriteEndElement();  // Property
            writer.WriteEndElement();  // Properties

            // Write out image node - we use a sample image, the marquis
            writer.WriteStartElement("Images");

            writer.WriteStartElement("Image");
            writer.WriteAttributeString("Name", "Peg Edit");
            writer.WriteAttributeString("Width", "25");
            writer.WriteAttributeString("Height", "25");
            writer.WriteAttributeString("Created", DateTime.Now.ToString());
            writer.WriteAttributeString("Encoding", IMAGE_ENCODING);
            writer.WriteString(MARQUIS);
            writer.WriteEndElement();  // Image

            writer.WriteEndElement();  // Images

            // End document
            writer.WriteEndDocument();
            writer.Flush();
            writer.Close();
         }
         catch (Exception ex)
         {
            // Pass exception up the chain
            throw (ex);
         }
      }


   }  // Class
}  // Namespace
